using System;
using System.Collections.Generic;
using System.Text;
using SharpMud.Server.Systems;
using SharpMud.MudLib.Common.Resourcing;
using System.IO;
using SharpMud.MudLib.Common.Entities.Players.Persistance;
using SharpMud.MudLib.Common.Entities.Players.Session.States;
using SharpMud.MudLib.Common.Entities.Players.Session;
using SharpMud.Net;

namespace SharpMud.MudLib.Common.Entities.Players
{
    /// <summary>
    /// Manages player loading and acquisition
    /// </summary>
    public class PlayerManager<TPlayerCharacterInstance> : PulsarServerSystem, IPlayerManager where TPlayerCharacterInstance : IPlayerCharacter, new()
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType);

        #region Private Variables
        internal const string _UriNodeName =                            "Players";
        private PrivateResourcingHelper                                 _ResourcingHelper;
        private IBaseMudLibRoot                                         _Host;
        private PlayerManagerConfiguration<TPlayerCharacterInstance>    _Configuration;
        private List<FileInfo>                                          _DetectedPlayerFiles;
        private List<FileSystemWatcher>                                 _FileSystemWatchers;
        private List<TPlayerCharacterInstance>                          _LoadedPlayers;
        #endregion

        #region Constructors
        private PlayerManager() { }
        public PlayerManager(PlayerManagerConfiguration<TPlayerCharacterInstance> configuration)
        {
            _Configuration = configuration;
        }
        #endregion      

        #region Public Properties
        public PlayerManagerConfiguration<TPlayerCharacterInstance> Configuration
        {
            get
            {
                return _Configuration;
            }
        }
        public virtual string RootPlayerFolder
        {
            get
            {
                return Host.GetSubfolderIntelligently(_Host.RootDataFolder, "players");
            }
        }
        public new IBaseMudLibRoot Host
        {
            get
            {
                return (IBaseMudLibRoot)base.Host;
            }
        }
        #endregion

        #region Private Methods
        private IPlayerCharacterLoadResults<TPlayerCharacterInstance> LoadPlayer(FileInfo file)
        {
            if (file == null)
                throw new ArgumentNullException("file");

            IPlayerCharacterStreamer<TPlayerCharacterInstance> playerStreamer = null;

            foreach (PlayerCharacterLoaderMetadatum<TPlayerCharacterInstance> pclm in _Configuration.PlayerLoaders)
            {
                if (file.Extension.Trim().ToLower() == pclm.Extension.Trim().ToLower())
                {
                    playerStreamer = pclm.GetPlayerCharacterStreamer();
                    break;
                }
            }
            if (playerStreamer == null)
            {
                string rel = (new Uri(RootPlayerFolder)).MakeRelativeUri(new Uri(file.FullName)).ToString();
                log.Error("Unable to create an IPlayerCharacterStreamer to load pfile \"" + rel + "\"");
                return null;
            }

            System.IO.Stream playerFileStream = file.OpenRead();
            IPlayerCharacterLoadResults<TPlayerCharacterInstance> results = playerStreamer.LoadPlayerCharacter(playerFileStream);
            playerFileStream.Close();
            return results;
        }
        #endregion

        #region FileSystemWatcher's Event Handlers
        void fileSystemWatchers_Renamed(object sender, RenamedEventArgs e)
        {
            foreach (FileInfo fi in _DetectedPlayerFiles)
            {
                if (fi.FullName == e.OldFullPath)
                {
                    _DetectedPlayerFiles.Remove(fi);
                    break;
                }
            }
            _DetectedPlayerFiles.Add(new FileInfo(e.FullPath));
            if (_Configuration.LogAllPfileEvents)
            {
                string oldRel = (new Uri(RootPlayerFolder)).MakeRelativeUri(new Uri(e.OldFullPath)).ToString();
                string newRel = (new Uri(RootPlayerFolder)).MakeRelativeUri(new Uri(e.FullPath)).ToString();
                log.Info("Pfile rename detected: \"" + oldRel + "\" -> \"" + newRel + "\"");
            }
        }

        void fileSystemWatchers_Deleted(object sender, FileSystemEventArgs e)
        {
            foreach (FileInfo fi in _DetectedPlayerFiles)
            {
                if (fi.FullName == e.FullPath)
                {
                    _DetectedPlayerFiles.Remove(fi);
                    break;
                }
            }
            if (_Configuration.LogAllPfileEvents)
            {
                string rel = (new Uri(RootPlayerFolder)).MakeRelativeUri(new Uri(e.FullPath)).ToString();
                log.Warn("Pfile deletion detected: \"" + rel + "\"");
            }
        }

        void fileSystemWatchers_Created(object sender, FileSystemEventArgs e)
        {
            _DetectedPlayerFiles.Add(new FileInfo(e.FullPath));
            if (_Configuration.LogAllPfileEvents)
            {
                string rel = (new Uri(RootPlayerFolder)).MakeRelativeUri(new Uri(e.FullPath)).ToString();
                log.Info("Pfile creation detected: \"" + rel + "\"");
            }
        }

        void fileSystemWatchers_Changed(object sender, FileSystemEventArgs e)
        {
            if (_Configuration.LogAllPfileEvents)
            {
                string rel = (new Uri(RootPlayerFolder)).MakeRelativeUri(new Uri(e.FullPath)).ToString();
                log.Debug("Pfile change detected: \"" + rel + "\"");
            }
        }
        #endregion

        #region Optional Overrides
        protected override void _Initialize()
        {
            base._Initialize();
            this._Host = (IBaseMudLibRoot)base.Host;
            _ResourcingHelper = new PrivateResourcingHelper(new Uri(_Host.BaseUri, _UriNodeName));
            _DetectedPlayerFiles = new List<FileInfo>();
            _FileSystemWatchers = new List<FileSystemWatcher>();

            foreach (PlayerCharacterLoaderMetadatum<TPlayerCharacterInstance> pclm in this._Configuration.PlayerLoaders)
            {
                _DetectedPlayerFiles.AddRange(pclm.GetApplicableFiles(RootPlayerFolder));
                _FileSystemWatchers.Add(pclm.GetFileSystemWatcher());
            }
            foreach (FileSystemWatcher fileSystemWatcher in _FileSystemWatchers)
            {
                fileSystemWatcher.Changed += new FileSystemEventHandler(fileSystemWatchers_Changed);
                fileSystemWatcher.Created += new FileSystemEventHandler(fileSystemWatchers_Created);
                fileSystemWatcher.Deleted += new FileSystemEventHandler(fileSystemWatchers_Deleted);
                fileSystemWatcher.Renamed += new RenamedEventHandler(fileSystemWatchers_Renamed);
            }
        }
        #endregion

        #region IPlayerManager Members

        public bool IsValidPlayerName(string potentialPlayerName)
        {
            //This has to be rooted in the configuration somewhere
            throw new Exception("The method or operation is not implemented.");
        }       

        public bool IsPlayerPlaying(string playerName)
        {
            //Need to add player session management
            throw new Exception("The method or operation is not implemented.");
        }

        public bool IsPlayerPlaying(IPlayerCharacter playerCharacter)
        {
            //Need to add player session management
            throw new Exception("The method or operation is not implemented.");
        }

        public ISessionState<TelnetPlayerSession> GetStartingTelnetSessionState(INetworkConnection connection)
        {
            return _Configuration.GetStartingTelnetSessionState(connection);
        }

        public bool PlayerExists(string playerName)
        {
            if (GetPlayerFileInfo(playerName).Exists)
                return true;
            if (IsPlayerLoaded(playerName))
                return true;
            return false;
        }
        
        public bool IsPlayerLoaded(string playerName)
        {
            string pn = playerName.Trim().ToLower();
            foreach (TPlayerCharacterInstance player in _LoadedPlayers)
            {
                if (player.PlayerName.Trim().ToLower() == pn)
                {
                    return true;
                }
            }
            return false;
        }

        public IPlayerCharacter GetPlayer(string playerName)
        {
            return GetPlayer(playerName, true);
        }

        public IPlayerCharacter GetPlayer(string playerName, bool loadIfNeccesary)
        {
            string pn = playerName.Trim().ToLower();
            foreach (TPlayerCharacterInstance player in _LoadedPlayers)
            {
                if (player.PlayerName.Trim().ToLower() == pn)
                {
                    return (IPlayerCharacter)player;
                }
            }

            if (loadIfNeccesary)
            {
                FileInfo file = GetPlayerFileInfo(playerName);
                if (!file.Exists)
                    return null;
                IPlayerCharacterLoadResults<TPlayerCharacterInstance> playerLoadResults;
                playerLoadResults = LoadPlayer(file);
                if (playerLoadResults.LoadedPlayer != null)
                {
                    _LoadedPlayers.Add(playerLoadResults.LoadedPlayer);
                    playerLoadResults.LoadedPlayer.Initialize(this);

                    if (playerLoadResults.Messages.Length > 0)
                    {
                        log.Info("Player \"" + playerName + "\" load SUCCESSFUL, with alerts" + System.Environment.NewLine);
                    }
                    else
                    {
                        log.Info("Player \"" + playerName + "\" load SUCCESSFUL.");
                    }
                }
                else
                {
                    if (playerLoadResults.Messages.Length > 0)
                    {
                        log.Warn("Player \"" + playerName + "\" load FAILED, with alerts" + System.Environment.NewLine);
                    }
                    else
                    {
                        log.Error("Player \"" + playerName + "\" load FAILED!");
                    }
                }
                foreach (IPlayerCharacterLoadMessage pclmsg in playerLoadResults.Messages)
                {
                    
                    if (pclmsg.IsCritical)
                        log.Error("\t[" + pclmsg.Row.ToString() + "," + pclmsg.Column.ToString() + "][!]: " + pclmsg.Message);
                    else
                        log.Warn( "\t[" + pclmsg.Row.ToString() + "," + pclmsg.Column.ToString() + "][ ]: " + pclmsg.Message);
                }
                return playerLoadResults.LoadedPlayer;
            }
            
            return null;
        }

        public void SavePlayer(IPlayerCharacter player)
        {
            if (!(player is TPlayerCharacterInstance))
                throw new InvalidCastException();
            else
                SavePlayer((TPlayerCharacterInstance)player);
        }

        public void SavePlayer(TPlayerCharacterInstance player)
        {
            if (player == null)
                throw new ArgumentNullException();
            if (!this.IsPlayerLoaded(player.Name))
                return;

            System.IO.FileInfo file = this.GetPlayerFileInfo(player.Name);
            if (file == null)
                throw new InvalidOperationException();

            IPlayerCharacterStreamer<TPlayerCharacterInstance> playerStreamer = null;

            foreach (PlayerCharacterLoaderMetadatum<TPlayerCharacterInstance> pclm in _Configuration.PlayerLoaders)
            {
                if (file.Extension.Trim().ToLower() == pclm.Extension.Trim().ToLower())
                {
                    playerStreamer = pclm.GetPlayerCharacterStreamer();
                    break;
                }
            }
            string rel = (new Uri(RootPlayerFolder)).MakeRelativeUri(new Uri(file.FullName)).ToString();
            if (playerStreamer == null)
            {
                System.Exception e = new Exception("Unable to create an IPlayerCharacterStreamer to save player file");
                log.Error("Unable to create an IPlayerCharacterStreamer to save pfile \"" + rel + "\"",e);
                throw e; 
            }
            log.Debug("Saving player '" + player.Name + "' -> '" + rel + "'...");
            System.IO.Stream playerFileStream = file.Open(FileMode.Create);
            IPlayerCharacterSaveResults playerSaveResults = playerStreamer.SavePlayerCharacter(playerFileStream, player);
            playerFileStream.Close();
            foreach (IPlayerCharacterSaveMessage pclmsg in playerSaveResults.Messages)
            {

                if (pclmsg.IsCritical)
                    log.Error("\t[!]: " + pclmsg.Message);
                else
                    log.Warn( "\t[ ]: " + pclmsg.Message);
            }
            log.Info("Player '" + player.Name + "' SAVED");
        }

        public FileInfo GetPlayerFileInfo(string playerName)
        {
            foreach (FileInfo fi in _DetectedPlayerFiles)
            {
                if (System.IO.Path.GetFileNameWithoutExtension(fi.FullName).Trim().ToLower() == playerName.Trim().ToLower())
                {
                    return fi;
                }
            }
            foreach (PlayerCharacterLoaderMetadatum<TPlayerCharacterInstance> pclm in _Configuration.PlayerLoaders)
            {
                return new FileInfo(Path.Combine(RootPlayerFolder, playerName.Trim().ToLower() + "." + pclm.Extension));
            }
            return null;
        }

        #endregion

        #region IGuidIdentifiedResourceHost Members

        public bool IsDefined(Guid guid)
        {
            foreach (TPlayerCharacterInstance player in this._LoadedPlayers)
            {
                if (((IHasInstanceGuid)player).ID == guid)
                    return true;
            }
            return false;     
        }

        public object GetResource(Guid guid)
        {
            foreach (TPlayerCharacterInstance player in this._LoadedPlayers)
            {
                if (((IHasInstanceGuid)player).ID == guid)
                    return player;
            }
            return null;
        }

        #endregion

        #region IUriIdentifiedResourceHost Members

        public Uri BaseUri
        {
            get { return _ResourcingHelper.BaseUri; }
        }

        public bool IsDefined(Uri resourceIndicator)
        {
            string playerName = BaseUri.MakeRelativeUri(resourceIndicator).Segments[0];
            return this.PlayerExists(playerName);
        }

        public object GetResource(Uri resourceIndicator)
        {
            string playerName = BaseUri.MakeRelativeUri(resourceIndicator).Segments[0];
            return this.GetPlayer(playerName, true);            
        }

        #endregion

    }
}